﻿using Cheems.Hikvision.Common;
using Cheems.Hikvision.Interface;
using Cheems.Hikvision.Models;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Channels;
using System.Threading.Tasks;
using static Cheems.Hikvision.CHCNetSDK;

namespace Cheems.Hikvision
{
    /// <summary>
    /// 摄像机服务类，用于与海康威视摄像机进行交互，支持登录、获取图片、视频流播放等功能。
    /// </summary>
    public class HikVideoService : IHikVideo
    {
        #region 事件
        public event Action<HikVideo, Bitmap> OnReceiveSingleFrame;
        public event Action<HikVideo, uint> OnSDKException;
        #endregion

        #region 私有字段
        private HikVideo _hikVideo;

        private bool m_bInitSDK = false;
        private CHCNetSDK.NET_DVR_USER_LOGIN_INFO struLogInfo;
        private CHCNetSDK.NET_DVR_DEVICEINFO_V40 DeviceInfo;
        private Int32 m_lUserID = -1;
        private uint iLastErr = 0;
        private Int32 m_lRealHandle = -1;
        private CHCNetSDK.REALDATACALLBACK RealData = null;
        private PlayCtrl.DECCBFUN m_fDisplayFun = null;
        private Int32 m_lPort = -1;
        private IntPtr m_ptrRealHandle;
        #endregion

        #region 构造函数
        public HikVideoService()
        {
            // SDK初始化
            m_bInitSDK = CHCNetSDK.NET_DVR_Init();
            if (m_bInitSDK == false)
            {
                return;
            }
            else
            {
                // 保存SDK日志 To save the SDK log
                CHCNetSDK.NET_DVR_SetLogToFile(3, "C:\\SdkLog\\", true);
            }
        }
        #endregion

        #region 相机登录
        /// <summary>
        /// 相机登录
        /// </summary>
        /// <param name="hikVideo"></param>
        /// <returns></returns>
        public bool Login(HikVideo hikVideo)
        {
            this._hikVideo = hikVideo;

            struLogInfo = new CHCNetSDK.NET_DVR_USER_LOGIN_INFO();
            //设备IP地址或者域名
            byte[] byIP = System.Text.Encoding.Default.GetBytes(this._hikVideo.ip);
            struLogInfo.sDeviceAddress = new byte[129];
            byIP.CopyTo(struLogInfo.sDeviceAddress, 0);
            //设备用户名
            byte[] byUserName = System.Text.Encoding.Default.GetBytes(this._hikVideo.userName);
            struLogInfo.sUserName = new byte[64];
            byUserName.CopyTo(struLogInfo.sUserName, 0);
            //设备密码
            byte[] byPassword = System.Text.Encoding.Default.GetBytes(this._hikVideo.password);
            struLogInfo.sPassword = new byte[64];
            byPassword.CopyTo(struLogInfo.sPassword, 0);
            //设备服务端口号
            struLogInfo.wPort = ushort.Parse(Convert.ToString(this._hikVideo.port));
            //是否异步登录：0- 否，1- 是 
            struLogInfo.bUseAsynLogin = false;
            DeviceInfo = new CHCNetSDK.NET_DVR_DEVICEINFO_V40();
            //登录设备 Login the device
            m_lUserID = CHCNetSDK.NET_DVR_Login_V40(ref struLogInfo, ref DeviceInfo);
            if (m_lUserID < 0)
            {
                // 登录失败
                iLastErr = CHCNetSDK.NET_DVR_GetLastError();
                OnSDKException?.Invoke(this._hikVideo, iLastErr);
                return false;
            }
            else
            {
                return true;
            }
        }
        #endregion

        #region 相机取图
        /// <summary>
        /// 取图并保存
        /// </summary>
        /// <param name="sJpegPicFileName"></param>
        public void GetPicAndSave(string sJpegPicFileName = "HikVideoJpeg.jpg")
        {
            CHCNetSDK.NET_DVR_JPEGPARA lpJpegPara = new CHCNetSDK.NET_DVR_JPEGPARA();
            lpJpegPara.wPicQuality = 0; //图像质量 Image quality
            lpJpegPara.wPicSize = 0xff; //抓图分辨率 Picture size: 2- 4CIF，0xff- Auto(使用当前码流分辨率)，抓图分辨率需要设备支持，更多取值请参考SDK文档
            //JPEG抓图 Capture a JPEG picture
            if (!CHCNetSDK.NET_DVR_CaptureJPEGPicture(m_lUserID, _hikVideo.channel, ref lpJpegPara, sJpegPicFileName))
            {
                iLastErr = CHCNetSDK.NET_DVR_GetLastError();
                OnSDKException?.Invoke(this._hikVideo, iLastErr);
                return;
            }
            return;
        }

        /// <summary>
        /// 获取图片
        /// </summary>
        /// <param name="picSize">分配内存空间大小</param>
        /// <returns></returns>
        public byte[] GetImageData(uint picSize = 5 * 1024 * 1024)
        {
            byte[] jpegBuffer = new byte[picSize];
            CHCNetSDK.NET_DVR_JPEGPARA lpJpegPara = new CHCNetSDK.NET_DVR_JPEGPARA();
            lpJpegPara.wPicQuality = 0; //图像质量 Image quality
            lpJpegPara.wPicSize = 0xff; //抓图分辨率 Picture size: 2- 4CIF，0xff- Auto(使用当前码流分辨率)，抓图分辨率需要设备支持，更多取值请参考SDK文档
            uint sizeReturned = 0; // 返回的JPEG图片实际大小
            if (!CHCNetSDK.NET_DVR_CaptureJPEGPicture_NEW(m_lUserID, _hikVideo.channel, ref lpJpegPara, jpegBuffer, picSize, ref sizeReturned))
            {
                iLastErr = CHCNetSDK.NET_DVR_GetLastError();
                OnSDKException?.Invoke(this._hikVideo, iLastErr);
                return null;
            }
            else
            {
                // 使用 ArraySegment 来避免复制
                ArraySegment<byte> resultSegment = new ArraySegment<byte>(jpegBuffer, 0, (int)sizeReturned);
                return resultSegment.Array.Skip(resultSegment.Offset).Take(resultSegment.Count).ToArray();
            }
        }

        /// <summary>
        /// 获取图片并转换为 Bitmap
        /// </summary>
        /// <param name="picSize">分配内存空间大小</param>
        /// <returns>返回捕获的图像 Bitmap</returns>
        public Bitmap GetImageAsBitmap(uint picSize = 5 * 1024 * 1024)
        {
            byte[] imageData = GetImageData(picSize);

            if (imageData == null)
            {
                return null;
            }

            // 将 byte[] 转换为 Bitmap
            using (MemoryStream ms = new MemoryStream(imageData))
            {
                return new Bitmap(ms);
            }
        }
        #endregion

        #region 取流
        /// <summary>
        /// 打开取流
        /// </summary>
        public void OpenStreaming()
        {
            CHCNetSDK.NET_DVR_PREVIEWINFO lpPreviewInfo = default(CHCNetSDK.NET_DVR_PREVIEWINFO);
            lpPreviewInfo.hPlayWnd = IntPtr.Zero; // 无预览窗口
            lpPreviewInfo.lChannel = this._hikVideo.channel;
            lpPreviewInfo.dwStreamType = 0u;
            lpPreviewInfo.dwLinkMode = 0u;
            lpPreviewInfo.bBlocked = true;
            lpPreviewInfo.dwDisplayBufNum = 1u;
            lpPreviewInfo.byProtoType = 0;
            lpPreviewInfo.byPreviewMode = 0;

            if (RealData == null)
            {
                RealData = RealDataCallBack;
            }
            m_lRealHandle = CHCNetSDK.NET_DVR_RealPlay_V40(m_lUserID, ref lpPreviewInfo, RealData, default(IntPtr));
            if (m_lRealHandle < 0)
            {
                iLastErr = CHCNetSDK.NET_DVR_GetLastError();
                OnSDKException?.Invoke(this._hikVideo, iLastErr);
            }
        }
        private void RealDataCallBack(int lRealHandle, uint dwDataType, IntPtr pBuffer, uint dwBufSize, IntPtr pUser)
        {
            switch (dwDataType)
            {
                case CHCNetSDK.NET_DVR_SYSHEAD:
                    //获取播放句柄 Get the port to play
                    if (!PlayCtrl.PlayM4_GetPort(ref m_lPort))
                    {
                        iLastErr = PlayCtrl.PlayM4_GetLastError(m_lPort);
                        OnSDKException?.Invoke(this._hikVideo, iLastErr);
                        break;
                    }

                    //设置流播放模式 Set the stream mode: real-time stream mode
                    if (!PlayCtrl.PlayM4_SetStreamOpenMode(m_lPort, PlayCtrl.STREAME_REALTIME))
                    {
                        iLastErr = PlayCtrl.PlayM4_GetLastError(m_lPort);
                        OnSDKException?.Invoke(this._hikVideo, iLastErr);
                    }

                    //打开码流，送入头数据 Open stream
                    if (!PlayCtrl.PlayM4_OpenStream(m_lPort, pBuffer, dwBufSize, 2 * 1024 * 1024))
                    {
                        iLastErr = PlayCtrl.PlayM4_GetLastError(m_lPort);
                        OnSDKException?.Invoke(this._hikVideo, iLastErr);
                        break;
                    }

                    //设置显示缓冲区个数 Set the display buffer number
                    if (!PlayCtrl.PlayM4_SetDisplayBuf(m_lPort, 15))
                    {
                        iLastErr = PlayCtrl.PlayM4_GetLastError(m_lPort);
                        OnSDKException?.Invoke(this._hikVideo, iLastErr);
                    }

                    //设置解码回调函数，获取解码后音视频原始数据 Set callback function of decoded data
                    m_fDisplayFun = new PlayCtrl.DECCBFUN(DecCallbackFUN);
                    if (!PlayCtrl.PlayM4_SetDecCallBackEx(m_lPort, m_fDisplayFun, IntPtr.Zero, 0))
                    {
                        //this.BeginInvoke( AlarmInfo, "PlayM4_SetDisplayCallBack fail" );
                    }

                    //开始解码 Start to play                       
                    if (!PlayCtrl.PlayM4_Play(m_lPort, m_ptrRealHandle))
                    {
                        iLastErr = PlayCtrl.PlayM4_GetLastError(m_lPort);
                        OnSDKException?.Invoke(this._hikVideo, iLastErr);
                        break;
                    }
                    break;
                default: //the other data
                    if (dwBufSize > 0 && m_lPort != -1)
                    {
                        for (int i = 0; i < 999; i++)
                        {
                            //送入码流数据进行解码 Input the stream data to decode
                            if (!PlayCtrl.PlayM4_InputData(m_lPort, pBuffer, dwBufSize))
                            {
                                iLastErr = PlayCtrl.PlayM4_GetLastError(m_lPort);
                                OnSDKException?.Invoke(this._hikVideo, iLastErr);
                                Thread.Sleep(2);
                            }
                            else
                                break;
                        }
                    }
                    break;
            }
        }
        //解码回调函数
        private void DecCallbackFUN(int nPort, IntPtr pBuf, int nSize, ref PlayCtrl.FRAME_INFO pFrameInfo, int nReserved1, int nReserved2)
        {
            if (pFrameInfo.nType == 3) //#define T_YV12   3
            {
                byte[] byteBuffYV12 = new byte[nSize];
                Marshal.Copy(pBuf, byteBuffYV12, 0, nSize);

                long lRGBSize = (long)pFrameInfo.nWidth * pFrameInfo.nHeight * 4;
                byte[] bufferRGB32 = new byte[lRGBSize];
                CommonFun.YV12_to_RGB32(byteBuffYV12, bufferRGB32, pFrameInfo.nWidth, pFrameInfo.nHeight);
                byteBuffYV12 = null;

                Bitmap bmpFromGRB32 = CommonFun.RGB32_to_Image(bufferRGB32, pFrameInfo.nWidth, pFrameInfo.nHeight);
                bufferRGB32 = null;
                if (null == bmpFromGRB32)
                    return;
                OnReceiveSingleFrame?.Invoke(_hikVideo, bmpFromGRB32);
            }
        }
        #endregion

        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            if (m_lRealHandle >= 0)
            {
                CHCNetSDK.NET_DVR_StopRealPlay(m_lRealHandle);
            }
            if (m_bInitSDK == true)
            {
                CHCNetSDK.NET_DVR_Cleanup();
            }
        }
    }
}
